import { Buffer } from 'node:buffer'
import http from 'node:http'
import querystring from 'node:querystring'
import { Stream } from 'node:stream'
import { url2queryObj } from '../core/utils'

function setHeader(res: any, val: any, headers: Record<string, any>) {
  function _setHeader() {
    if (headers['Content-Type']) return
    // no content
    if (!val) {
      headers['Content-Type'] = 'text/plain;charset=utf-8'
      return
    }

    // string
    if (typeof val === 'string') {
      if (!headers['Transfer-Encoding']) {
        headers['Content-Length'] = Buffer.byteLength(val)
      }
      headers['Content-Type'] = /^\s*</.test(val) ? 'text/html;charset=utf-8' : 'text/plain;charset=utf-8'
      return
    }

    const setType = !headers['Content-Type']

    // buffer
    if (Buffer.isBuffer(val)) {
      if (setType) headers['Content-Type'] = 'binary'
      headers['Content-Length'] = val.length
      return
    }

    // ReadableStream
    if (val instanceof ReadableStream) {
      if (setType) headers['Content-Type'] = 'binary'
      return
    }

    // blob
    if (val instanceof Blob) {
      if (setType) headers['Content-Type'] = 'image/png'
      headers['Content-Length'] = val.size
      return
    }
    if (typeof val === 'object') {
      headers['Content-Type'] = 'application/json;charset=utf-8'
      delete headers['Content-Length']
    }
  }
  _setHeader()

  if (Object.keys(headers).length) {
    for (const key in headers) {
      res.setHeader(key, `${headers[key]}`)
    }
  }
}

function response(res: any, body: any, statusCode: number) {
  if (!statusCode) {
    return res.end()
  }
  res.statusCode = statusCode
  if (!body) res.statusCode = 204

  if (!body) return res.end()

  if (Buffer.isBuffer(body)) return res.end(body)
  if (typeof body === 'string') return res.end(body)
  if (body instanceof Blob) return Stream.Readable.from(body.stream()).pipe(res)
  if (body instanceof ReadableStream) return Stream.Readable.from(body).pipe(res)
  if (body instanceof Response) return Stream.Readable.from(body?.body || '').pipe(res)
  if (body instanceof Stream) return body.pipe(res)

  // body: json
  res.end(JSON.stringify(body))
}

export default function nodeServerWrap(callback: (req: any, res?: any) => Promise<any>, port = 3000) {
  const server = http.createServer()
  server.on('request', (req, res) => {
    const method = req.method
    if (method === 'GET') {
      setResponse({})
    }
    else
      if (method === 'POST') {
        let chunks = ''
        req.on('data', (chunk) => {
          chunks += chunk
        })
        req.on('end', () => {
          const headers = req.headers
          setResponse(formatData(chunks, headers['content-type'] || 'application/json'))
        })
      }
      else {
        res.writeHead(405, { 'Content-Type': 'application/json' })
        res.end(JSON.stringify({ error: 'Method Not Allowed' }))
      }
    async function setResponse(postBody: querystring.ParsedUrlQuery) {
      const [path, ...query] = req.url?.split('?') || []

      const ctxReq = Object.assign(req, { path, query: url2queryObj(query.join('')), querystring: query.join(''), body: postBody })
      const ctx = await callback(ctxReq, res)

      const _headers = ctx.response.headers
      const body = ctx.response.body
      const statusCode = ctx.response.status

      setHeader(res, body, _headers)
      response(res, body, statusCode)
    }
  })
  server.listen(port, () => {
    console.log(`server is running at http://127.0.0.1:${port}`)
  })
}
function formatData(str: any, contentType: string) {
  if (contentType.includes('text/plain')) return str
  if (contentType.includes('application/json')) return JSON.parse(str)
  if (contentType.includes('application/x-www-form-urlencoded')) return querystring.parse(str)
  return str
}
